<?php

require_once('../data.inc');

class Category 
{
	function __construct($nm) 
	{
		global $db;
		$q = new Query($db);
		$q->exec("
			SELECT tname, descr, 
			       canAdd, canEdit, canDel,
				    view_link, edit_link, add_link, del_link
			FROM cf_categories
			WHERE name='$nm'
		");
		if (!$q->fetch()) {
			throw new Exception("Invalid category name: $nm. No such category in cf_categories");
		}
		$this->tname = $q->at(0);
		$this->descr = $q->at(1);
		$this->canAdd = ($q->at(2) == 'Y');
		$this->canEdit = ($q->at(3) == 'Y');
		$this->canDel = ($q->at(4) == 'Y');
		$this->viewLink = $q->at(5);
		$this->editLink = $q->at(6);
		$this->addLink = $q->at(7);
		$this->delLink = $q->at(8);
	}
	
	public function tableName() { 
		return $this->tname;
	}
		
	public function descr() { 
		return $this->descr;
	}
	
	public function canAdd() { 
		return $this->canAdd;
	}
	
	public function canEdit() { 
		return $this->canEdit;
	}
	
	public function canDel() { 
		return $this->canDel;
	}
	
	public function viewLink() {
		return $this->viewLink;
	}
	
	public function editLink() { 
		return $this->editLink;
	}

	public function addLink() { 
		return $this->addLink;
	}

	public function delLink() { 
		return $this->delLink;
	}
	
	private $tname = '';
	private $descr = '';
	private $canAdd = false;
	private $canEdit = false;
	private $canDel = false;
	private $viewLink = '';
	private $editLink = '';
	private $addLink = '';
	private $delLink = '';
};




class Fields 
{
	function Fields($catNm, $fnm=null) 
	{
		global $db;
		$this->q = new Select($db,'cf_fields');
		$this->q->addFields(
			'name','is_key','view',
			'edit','descr','value','filter','sort',
			'size','style','type','length','fmt','nullAvail',
			'sortAllow', 'filterAllow', 'help'
		);

		$this->q->where('cat_name',$catNm);		
		$this->q->order('pos',ASC);

		if (!is_null($fnm)) {
			$this->q->where('name',$fnm);
		}
	}
	
	function open() {
		return $this->q->run();
	}
	function eof() {
		return $this->q->eof();
	}
	function reset() {
		return $this->q->restart();
	}
	function fetch() {
		return $this->q->fetch();
	}
	
	function name() { 
		return $this->q->at(0);
	}
	function nameInSelect() {
		if ($this->fmt()) {
			if ($this->type() == FIELD_TYPE_DATETIME || $this->type() == FIELD_TYPE_DATE) {
				return dbDateFormat($this->name(), $this->fmt());
			}
		}
		return $this->name();
	}
	function descr() {
		return $this->q->at(4);
	}
	function isKey() { 
		return ($this->q->at(1) == 'Y');
	}
	function type() {
		return $this->q->at(10);
	}
	function isViewable() {
		return ($this->q->at(2) == 'Y');
	}
	function isFiltered() {
		return $this->q->at(6) ? true : false;
	}
	function filter() {
		return $this->q->at(6);
	}	
	function isSorted() {
		return $this->q->at(7) != '';
	}
	function sort() {
		return $this->q->at(7);
	}
	function size() {
		return $this->q->at(8);
	}
	function style() {
		return $this->q->at(9);
	}	
	function isEditable() {
		return ($this->q->at(3) == 'Y');
	}
	function length() {
		return $this->q->at(11);
	}
	function fmt() {
		return $this->q->at(12);
	}	
	function defaultValue() {
		return $this->q->at(5);
	}
	function isNullAllowed() {
		return ($this->q->at(13) == 'Y');
	}
	function isSortAllowed() {
		return ($this->q->at(14) == 'Y');
	}
	function isFilterAllowed() {
		return ($this->q->at(15) == 'Y');
	}
	function hasHelp() {
		return !is_null($this->q->at(16));
	}

//private
	var $q;
};



function decFieldName($fnm) {
	return str_replace('!','.',$fnm);
}

function encFieldName($fnm) {
	return str_replace('.','!',$fnm);
}




/*************************************************************************
*
* Edit controls for different types of fields
*
*************************************************************************/
class EditControl 
{
	function EditControl($name) {
		$this->name = $name;
	}
	
	function type() {}
	
	function setValue($val) {
		$this->val = $val;
	}
	
	function render() {}
	
//protected:
	var $name;
	var $val;
};



class StandardEditControl extends EditControl 
{
	function StandardEditControl($name, $length, $nullAllow, $readOnly) 
	{
		$this->EditControl($name);
		$this->length = $length;
		$this->scale = 0;
		$this->nullAllow = $nullAllow;
		$this->readOnly = $readOnly;
		$this->setValue('');
	}
	
	function setValue($val) {
		$this->val = "'$val'";
	}
	
	function render() 
	{
		$txt = <<<EOD
			<span id='{NAME}'></span>
			<script lang=javascript>
				document.getElementById('{NAME}').
					appendChild(
						create_control(
							{TYPE},
							'{NAME}',
							{VALUE},
							{LENGTH},
							{SCALE},
							{NULLALLOW},
							{READONLY}
						)
					);
			</script>
EOD;

		return 
			str_replace(
				array('{NAME}', '{TYPE}', '{VALUE}', '{LENGTH}', '{SCALE}', '{NULLALLOW}', '{READONLY}'), 
				array(
					$this->name, 
					$this->innerType(), 
					$this->val, 
					$this->length,
					$this->scale,
					$this->nullAllow ? '1' : '0', 
					$this->readOnly ? '1' : '0'
				),
				$txt
			);
	}

//protected:
	function innerType() { die("StandardEditControl::innerType"); }

	var $length;
	var $scale;
	var $nullAllow;
	var $readOnly;
};



class StringEditControl extends StandardEditControl 
{
	function type() { return FIELD_TYPE_STRING; }

	function setValue($val) {
		parent::setValue(addslashes($val));
	}
	
//protected:
	function innerType() { return 'ftString'; }
};



class IntegerEditControl extends StandardEditControl 
{
	function type() { return FIELD_TYPE_INTEGER; }

//protected:
	function innerType() { return 'ftInteger'; }
};


class FloatEditControl extends StandardEditControl 
{
	function type() { return FIELD_TYPE_FLOAT; }
	
	function setScale($scale) {
		$this->scale = $scale;
	}

//protected:
	function innerType() { return 'ftFloat'; }
};


class DateTimeEditControl extends StandardEditControl 
{
	function DateTimeEditControl($name, $nullAllow, $readOnly) 
	{
		$this->StandardEditControl($name,0,$nullAllow,$readOnly);
		//length=0 - subtype of datetime control
	}

	function type() { return FIELD_TYPE_DATETIME; }
	
	function setValue($val) {
		if (!empty($val)) {
			$this->val = "parseDate('$val', MYSQL_DATETIME_FORMAT)";
			return;
		}
		parent::setValue($val);
	}

	
//protected:
	function innerType() { return 'ftTime'; }
}



class DateEditControl extends StandardEditControl 
{
	function DateEditControl($name, $nullAllow, $readOnly) 
	{
		$this->StandardEditControl($name,1,$nullAllow,$readOnly);
		//length=1 - subtype of date control
	}

	function type() { return FIELD_TYPE_DATE; }
	
	function setValue($val) {
		if (!empty($val)) {
			$this->val = "parseDate('$val', MYSQL_DATE_FORMAT)";
			return;
		}
		parent::setValue($val);
	}
	
//protected:
	function innerType() { return 'ftTime'; }
}


class ListEditControl 
	extends StandardEditControl 
{
	function __construct($name, $nullAllow, $readOnly) 
	{
		parent::__construct($name,0,$nullAllow,$readOnly);
	}
	
	public function type() 
	{ 
		return FIELD_TYPE_DICT; 
	}
	
	public function addOption($val, $text) 
	{
		$this->options[] = array($val, $text);
	}
	
	public function render() 
	{
		$txt = parent::render();
		
		$txt .= "<script lang=javascript>";
		foreach ($this->options as $option) {
			$txt .= "window['$this->name'].addOption('$option[1]', '$option[0]');";
		}
		$txt .= "</script>";
		
		return $txt;
	}
	
	public function innerType() 
	{ 
		return 'ftDict'; 
	}
	
	private $options = array();
};


class DictEditControl extends StandardEditControl 
{
	function DictEditControl($name, $nullAllow, $readOnly) 
	{
		$this->StandardEditControl($name,0,$nullAllow,$readOnly);
	}
	
	function create($tname, $idField, $valField, $where, $orderById=false) 
	{
		$this->tname = $tname;
		$this->idField = $idField;
		$this->valField = $valField;
		$this->where = $where;
		$this->orderById = $orderById;
	}
	
	function render() 
	{
		$txt = parent::render();
		
		global $db;
		$q = new Select($db, $this->tname);
		$q->addFields($this->idField, $this->valField);
		$q->addWhereString($this->where);
		if ($this->orderById) {
			$q->order($this->idField, ASC);
		}
		else {
			$q->order($this->valField, ASC);
		}
		$q->run();
		
		$txt .= "<script lang=javascript>";
		while ($row = $q->fetch()) 
		{
			$v = str_replace("'","\'",$row[1]);
			$txt .= "window['$this->name'].addOption('$v', '$row[0]');";
		}
		$txt .= "</script>";
		
		return $txt;
	}
	
	function type() { return FIELD_TYPE_DICT; }
	
//protected:
	function innerType() { return 'ftDict'; }

//private:
	var $tname;
	var $idField;
	var $valField;
	var $where;
	var $orderById;
};



class TextEditControl extends EditControl 
{
	function type() { return FIELD_TYPE_TEXT; }

	function render() 
	{
		$txt = <<<EOD
			<textarea name='{NAME}' cols=50 rows=20 wrap=soft>
				{VALUE}
			</textarea>
			<script lang=javascript>
				createEditor('{NAME}');
			</script>
EOD;
		return
			str_replace(
				array('{NAME}', '{VALUE}'), 
				array($this->name, $this->val),
				$txt
			);
	}
};



/*************************************************************************
*
* Edit fields
*
*************************************************************************/
class FieldControl 
{
	function FieldControl($name, $descr, $type, $defValue, $length, $fmt, $nullAvail, $posInSrc, $hasHelp) {
		$this->name = $name;
		$this->descr = $descr;
		$this->type = $type;
		$this->val = $defValue;
		$this->length = $length;
		$this->fmt = $fmt;
		$this->nullAvail = ($nullAvail ? 1 : 0);
		$this->posInSrc = $posInSrc;
		$this->hasHelp = $hasHelp;
	}
	
	function setValue(&$src) {
		if ($this->posInSrc >= 0) {
			$this->val = $src->at( $this->posInSrc );
		}
	}
				
	function render($txt) {
		global $catname;
		global $tname;
		
		$rawName = $this->name;
		$name = encFieldName($this->name);

		$ctltxt = '';
		$descr = $this->nullAvail ? $this->descr : $this->descr . "*";
		
		if ($this->type == FIELD_TYPE_FILE) {
			$files = new Files($catname);
			$finfo = $files->getFileInfo($rawName);
			$availTypes = null;
			foreach ($finfo['types'] as $ftype) {
				if ($availTypes) {
					$availTypes .= ',';
				}
				$availTypes .= $ftype;
			}
			$descr .= " ($availTypes)";
				
			global $keyFields;
			if ($keyFields) {
				$f = new Files($catname);
				$f->open($keyFields->asArray(), $rawName);
				if ($f->fetch()) {
					$keys = $keyFields->asParamString();
					$ctltxt .= (
						"<span id='file_$rawName'>".
							"<a href=javascript:openWindow('../../" . $f->current() ."')>Открыть</a>&nbsp;".
							"<a href=javascript:deleteFile('$catname','$rawName','$keys')>Удалить</a>".
						"</span>"
					);
				}
			}
			$ctltxt .= " Загрузить из <input type=file name='$name' />";
		}
		else {
			$ctl = null;
			
			$dict = new Dictionary($catname, $this->name);
			if ($dict->exist()) {
				$ctl = new DictEditControl($name, $this->nullAvail, false);
				$ctl->create(
					$dict->getName(),
					$dict->getKeyFieldName(),
					$dict->getValueFieldName(),
					$dict->getConditions()
				);
			}
			else {
				switch ($this->type) {
					case FIELD_TYPE_STRING:
						$ctl = new StringEditControl($name, $this->length, $this->nullAvail, false);
						break;
					case FIELD_TYPE_INTEGER:
						$ctl = new IntegerEditControl($name, $this->length, $this->nullAvail, false);
						break;
					case FIELD_TYPE_FLOAT:
						$ctl = new FloatEditControl($name, $this->length, $this->nullAvail, false);
						$ctl->setScale((int)$this->fmt);
						break;
					case FIELD_TYPE_DATETIME:
						$ctl = new DateTimeEditControl($name, $this->nullAvail, false);
						break;
					case FIELD_TYPE_DATE:
						$ctl = new DateEditControl($name, $this->nullAvail, false);
						break;
					case FIELD_TYPE_TEXT:
						$ctl = new TextEditControl($name);
						break;
					default:
						die("Invalid field type\nFieldControl");
				}
			}
				
			if ($this->type == FIELD_TYPE_TEXT) {
				$ctl->setValue( htmlspecialchars($this->val, ENT_NOQUOTES, 'cp1251') );
			}
			else {
				$ctl->setValue($this->val);
			}
			
			$ctltxt = $ctl->render();
			
			if ($this->type != FIELD_TYPE_TEXT) {
				$ctltxt .=
					"<script lang=javascript>" . 
					"	fields.push('$name');" .
					"  fieldsDescr.push('$this->descr');" .
					"</script>";
				$ctltxt .= "<input type=hidden name='$name'/>";
			}
		}
		
		$helpControl = '';
		if ($this->hasHelp) {
			$helpControl = "<a href=javascript:showHelp('$catname','$this->name')>?</a>";
		}
		
		echo 
			str_replace(
				array('{NAME}','{CONTROL}', '{HELP}'), 
				array($descr, $ctltxt, $helpControl), 
				$txt
			);
	}
	
	var
		$name,
		$descr,
		$type,
		$val,
		$length,
		$fmt,
		$nullAvail,
		$posInSrc;
};


/*************************************************************************
*
* Convert field value, getting system information about it
*
*************************************************************************/
class SysConverter
{
	function SysConverter($catname) {
		$this->catname = $catname;
	}
	
	function convert($fnm, $val) 
	{
		$fInfo = new Fields($this->catname, $fnm);
		$sql = $fInfo->open();
		$fInfo->fetch();
		if ($fInfo->eof()) {
			return $val;
		}
		if ($fInfo->type() == FIELD_TYPE_INTEGER) {
			if ($val == '') {
				return null;
			}
			return (int)$val;
		}
		return $val;
	}
	
//private:
	var $catname;
};

		
/*************************************************************************
*
* Manages operations with data
*
*************************************************************************/
class DataManager
{
	function __construct($catnm) 
	{
		$this->catname = $catnm;
		$this->success = false;
		
		$this->cat = new Category($catnm);
		$this->tname = $this->cat->tableName();
	}
	
	public function delete(&$keys)
	{
		global $db;
		$q = new Select($db, 'cf_dictionaries');
		$q->addFields('cat_name', 'field_name', 'dict_key');
		$q->where('dict_name', $this->tname);
		$q->run();
		
		while ($q->fetch()) 
		{
			$keyval = $this->getCurrKeyFieldValue($q->at(2), $keys);
			$childCat = new Category($q->at(0));
			
			if ($this->existChildRecs($childCat->tableName(), $q->at(1), $keyval)) 
			{
				$parentName = $this->cat->descr();
				$childName = $this->getSrcName($q->at(0));
				echo 
					"Невозможно удалить запись из '$parentName', так как существуют связанные с ней записи в '$childName'.".
					" Необходимо удалить сначала все связанные записи из '$childName'.";
				$this->success = false;
				return;
			}
		}
		
		$qDel = new Delete($db, $this->tname);
		$qDel->addWhereArray($keys);
		$this->sql = $qDel->run();
		
		$this->success = true;
	}
	
	public function insert(&$fields)
	{
		$this->setNullFieldsToDefault($fields);
		$this->processValues($fields);
		
		global $db;
		$q = new Insert($db, $this->tname);
		$q->addFields($fields);
		$this->sql = $q->run();
		
		$this->success = true;
	}
	
	public function update(&$fields, &$where)
	{
		$this->processValues($fields);
		$this->processValues($where);
				
		global $db;
		$q = new Update($db, $this->tname);
		$q->addFields($fields);
		$q->addWhereArray($where);
		$this->sql = $q->run();
		
		$this->success = true;
	}
	
	public function isSuccess() 
	{
		return $this->success;
	}
	
	public function getLastRunSQL() 
	{
		return $this->sql;
	}
	
	

	private function getCurrKeyFieldValue($keynm, &$keys) 
	{
		global $db;
		$q = new Select($db, $this->tname);
		$q->addFields($keynm);
		foreach ($keys as $fnm => $fval) {
			$q->where($fnm, $fval);
		}
		$q->run();
		if ($q->fetch()) {
			return $q->at(0);
		}
		die("There is no corresponding record in table $this->name\nDataManager.getCurrKeyFieldValue");
	}
	
	private function existChildRecs($tnm, $fnm, $keyval)
	{
		global $db;
		$q = new Select($db, $tnm);
		$q->addFields($fnm);
		$q->where($fnm, $keyval);
		$q->setLimit(1);
		$q->run();
		return $q->fetch() ? true : false;
	}
	
	private function getSrcName($nm) 
	{
		$cat = new Category($nm);
		return $cat->descr();
	}
	
	private function setNullFieldsToDefault(&$fields)
	{
		$fInfo = new Fields($this->catname);
		$fInfo->open();
		while ($fInfo->fetch()) {
			if (!$fInfo->isNullAllowed() && !isset($fields[$fInfo->name()])) {
				if ($fInfo->defaultValue()) {
					$fields[$fInfo->name()] = $fInfo->defaultValue();
				}
				else {
					die("Configuration error. No default value for field " . $fInfo->name());
				}
			}
		}
	}
	
	private function processValues(&$values)
	{
		$fInfo = new Fields($this->catname);
		$fInfo->open();
		while ($fInfo->fetch()) 
		{
			if ($fInfo->type() == FIELD_TYPE_INTEGER) {
				$this->processInt($fInfo->name(), $values);
			}
			elseif ($fInfo->type() == FIELD_TYPE_STRING) {
				$this->processString($fInfo->name(), $values);
			}
			elseif ($fInfo->type() == FIELD_TYPE_DATETIME || $fInfo->type() == FIELD_TYPE_DATE) {
				$this->processDateTime($fInfo->name(), $values);
			}
		}
	}

	private function processInt($nm, &$values)
	{
		if (isset($values[$nm])) {
			if ($values[$nm] == '') {
				$values[$nm] = null;
			}
			else {
				$values[$nm] = (int)$values[$nm];
			}
		}
	}
	
	private function processString($nm, &$values)
	{
		if (isset($values[$nm])) 
		{
			if (!strlen($values[$nm])) {
				$values[$nm] = null;
			}
		}
	}

	private function processDateTime($nm, &$values)
	{
		if (isset($values[$nm])) 
		{
			if (!strlen($values[$nm])) {
				$values[$nm] = null;
			}
		}
	}

		
	private $cat = null;
	private $catname;
	private $tname;
	private $sql;
	private $success;
};

?>